package de.g18.gruppe3.ldapinterface.service;

import java.util.ArrayList;
import java.util.List;

import javax.naming.AuthenticationException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;

import de.g18.gruppe3.common.exception.NotPersistentLDAPModelException;
import de.g18.gruppe3.common.exception.OrganizationalUnitAlreadyExistsException;
import de.g18.gruppe3.common.exception.OrganizationalUnitNotFoundException;
import de.g18.gruppe3.common.exception.ServiceException;
import de.g18.gruppe3.common.ldap.OrganizationalUnitService;
import de.g18.gruppe3.common.model.OrganizationalUnit;

/**
 * Implementierung des {@link OrganizationalUnitService}
 *
 * @author <a href="mailto:kevinhuber.kh@gmail.com">Kevin Huber</a>
 */
public class OrganizationalUnitServiceImpl implements OrganizationalUnitService {

    private static final LDAPContext LDAP = LDAPContext.getInstance();


    @Override
    public List<OrganizationalUnit> getAllOrganizationalUnits() {
        List<OrganizationalUnit> organizationalUnits = new ArrayList<OrganizationalUnit>();
        SearchControls controlls = new SearchControls();
        String[] resultAttributes = {"ou"};
        controlls.setReturningAttributes(resultAttributes);
        controlls.setSearchScope(SearchControls.ONELEVEL_SCOPE);

        String filter = "(objectClass=organizationalUnit)";
        try {
            NamingEnumeration<SearchResult> resultList = LDAP.search(LDAPContext.DOMAIN_ROOT, filter, controlls);

            while (resultList.hasMore()) {
                SearchResult result = resultList.next();
                Attributes attributes = result.getAttributes();

                OrganizationalUnit ou = new OrganizationalUnit((String) attributes.get("ou").get());
                ou.setDistinguishedName(generateOUDN(ou));
                ou.setPersisted(true);

                organizationalUnits.add(ou);
            }
        } catch (NamingException e) {
            throw new IllegalStateException("Organisationseinheiten konnten nicht geladen werden!", e);
        }
        return organizationalUnits;
    }

    @Override
    public OrganizationalUnit getOrganizationalUnitByName(String aName) throws OrganizationalUnitNotFoundException {
        SearchControls controlls = new SearchControls();
        String[] resultAttributes = {"ou"};
        controlls.setReturningAttributes(resultAttributes);
        controlls.setSearchScope(SearchControls.ONELEVEL_SCOPE);

        String filter = "(&(objectClass=organizationalUnit)(ou=" + aName + "))";
        try {
            NamingEnumeration<SearchResult> resultList =
                LDAP.search(LDAPContext.DOMAIN_ROOT, filter, controlls);

            if (!resultList.hasMore()) {
                throw new OrganizationalUnitNotFoundException(aName);
            }
            SearchResult result = resultList.next();
            Attributes attributes = result.getAttributes();

            OrganizationalUnit ou = new OrganizationalUnit((String) attributes.get("ou").get());
            ou.setDistinguishedName(generateOUDN(ou));
            ou.setPersisted(true);

            return ou;
        } catch (NamingException e) {
            throw new IllegalStateException("Organisationseinheit konnte nicht geladen werden!", e);
        }
    }

    @Override
    public OrganizationalUnit createOrganizationalUnit(OrganizationalUnit aOU) throws OrganizationalUnitAlreadyExistsException {
        Attributes container = new BasicAttributes();
        container.put(new OUAttributeClass());

        aOU.setDistinguishedName(generateOUDN(aOU));
        try {
            LDAP.createSubcontext(aOU.getDistinguishedName(), container);
        } catch (AuthenticationException e) {
            throw new OrganizationalUnitAlreadyExistsException(aOU);
        } catch (NamingException e) {
            throw new IllegalStateException("Fehler! Organisationseinheit konnte nicht angelegt werden!", e);
        }
        aOU.setPersisted(true);

        return aOU;
    }

    private String generateOUDN(OrganizationalUnit aOrganizationalUnit) {
        return "ou=" + aOrganizationalUnit.getName() + "," + LDAPContext.DOMAIN_ROOT;
    }

    @Override
    public void deleteOU(OrganizationalUnit aOU) throws ServiceException {
        if (!aOU.isPersisted()) {
            throw new NotPersistentLDAPModelException(OrganizationalUnit.class);
        }
        try {
            LDAP.destroySubcontext(aOU.getDistinguishedName());
        } catch (NamingException e) {
            throw new IllegalStateException("Organisationseinheit konnte nicht gelöscht werden!", e);
        }
    }


    // Inner Classes ***********************************************************

    /**
     * LDAP-OU Klasse
     */
    private class OUAttributeClass extends BasicAttribute {

        private static final long serialVersionUID = 100L;

        public OUAttributeClass() {
            super("objectClass");
            add("top");
            add("organizationalUnit");
        }
    }
}
